package com.mathdroid.model.stats;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.LinkedList;

import com.mathdroid.model.Model;
import com.mathdroid.model.buffs.BuffTemplate;
import com.mathdroid.model.enums.EItemStat;
import com.mathdroid.model.enums.ETalentTree;

/**
 * This implements specific Imperial Agent and Smuggler stat mechanics.
 */
public final class ImperialAgentCharacterStats extends ACharacterStats {
	
	/**
	 * The level of the character.
	 */
	private static final double LEVEL = 50d;
	
	/**
	 * This is for formating numbers.
	 */
	private DecimalFormat df = null;
	
	/**
	 * The primary stat of this specialization.
	 */
	private static final EItemStat PRIMARY_STAT = EItemStat.CUNNING;
	
	/**
	 * The critical strike chance bonus from the group buff (in percent).
	 */
	private static final double CRITICAL_STRIKE_CHANCE_GROUP_BUFF = 5d;
	
	/**
	 * Default Constructor.
	 */
	public ImperialAgentCharacterStats() {
		this.df = new DecimalFormat("#0.##");
		this.df.setDecimalSeparatorAlwaysShown(false);
		DecimalFormatSymbols symbols = new DecimalFormatSymbols();
		symbols.setDecimalSeparator('.');
		symbols.setGroupingSeparator(',');
		this.df.setDecimalFormatSymbols(symbols);
	}
	
	@Override
	public LinkedList<LinkedList<String>> getAllDataLists() {
		LinkedList<LinkedList<String>> lists = 
			new LinkedList<LinkedList<String>>();
		
		lists.add(this.getDataListBonusDamageHealing());
		lists.add(this.getDataListHit());
		lists.add(this.getDataListCriticalStrike());
		lists.add(this.getDataListCastingSpeed());
		lists.add(this.getDataListDefensive());
		lists.add(this.getDataListCompanion());
		lists.add(this.getDataListPvP());
		
		return lists;
	}
	
	@Override
	protected LinkedList<String> getDataListCastingSpeed() {
		LinkedList<String> list = new LinkedList<String>();
		
		/* Title: */
		list.add("Activation Speed");
		
		/* Alacrity related: */
		list.add("Activation Speed: " 
				+ this.format(this.getActivationSpeedDivisor() * 100d) + "%");
		
		return list;
	}
	
	@Override
	protected LinkedList<String> getDataListDefensive() {
		LinkedList<String> list = new LinkedList<String>();
		
		/* Title: */
		list.add("Defensive");
		
		/* Defensive stats: */
		list.add("Total health: " 
				+ this.format(this.getMaxHealth()));
		list.add("Health regeneration: " 
				+ this.format(this.getHealthRegen()));
		list.add("Passive damage reduction: " 
				+ this.format(this.getArmorDamageReductionInPercent()) + "%");
		list.add("Defense (Avoidance): " 
				+ this.format(this.getDefenseChancePercent()) + "%");
		
		return list;
	}
	
	@Override
	protected LinkedList<String> getDataListCompanion() {
		LinkedList<String> list = new LinkedList<String>();
		
		/* Title: */
		list.add("Companion");
		
		/* Companion: */
		list.add("Companion bonus health: " 
				+ this.format(this.getBonusCompanionHealth()));
		list.add("Companion bonus damage: " 
				+ this.format(this.getBonusCompanionDamage()) + "%");
		list.add("Companion bonus healing: " 
				+ this.format(this.getBonusCompanionHealing()) + "%");
		
		return list;
	}
	
	@Override
	protected LinkedList<String> getDataListPvP() {
		LinkedList<String> list = new LinkedList<String>();
		
		/* Title: */
		list.add("Player versus Player");
		
		/* PvP: */
		list.add("PvP bonus damage: " 
				+ this.format(this.getPvPBonusDamagePercent()) + "%");
		list.add("PvP damage reduction: " 
				+ this.format(this.getPvPDamageReductionPercent()) + "%");
		list.add("PvP bonus healing: " 
				+ this.format(this.getPvPBonusHealingPercent()) + "%");
		
		return list;
	}
	
	@Override
	protected LinkedList<String> getDataListHit() {
		LinkedList<String> list = new LinkedList<String>();
		
		/* Title: */
		list.add("Accuracy");
		
		/* Accuracy related. The Knife Off-Hand is not really an Off-Hand. */
		list.add("Basic ranged hit chance: " 
				+ this.format(this.getBasicAccuracyPercent()) + "%");
		list.add("Basic melee hit chance: " 
				+ this.format(this.getBasicAccuracyPercent()) + "%");
		list.add("Specials hit chance: " 
				+ this.format(this.getSpecialAccuracyPercent()) + "%");
		
		return list;
	}

	@Override
	protected LinkedList<String> getDataListCriticalStrike() {
		LinkedList<String> list = new LinkedList<String>();
		
		/* Title: */
		list.add("Critical Strikes");
		
		/* Critical strike related: */
		list.add("Tech critical chance: " 
				+ this.format(this.getCritChancePercent()) + "%");
		list.add("Ranged critical chance: " 
				+ this.format(this.getRangedCritChancePercent()) + "%");
		list.add("Melee critical chance: " 
				+ this.format(this.getMeleeCritChancePercent()) + "%");
		list.add("Force critical chance: " 
				+ this.format(this.getForceCritChancePercent()) + "%");
		list.add("Critical bonus damage: " 
				+ this.format(this.getCritDamageBonusPercent()) + "%");
		
		return list;
	}
	
	@Override
	protected LinkedList<String> getDataListBonusDamageHealing() {
		LinkedList<String> list = new LinkedList<String>();
		
		/* Title: */
		list.add("Bonus Damage and Healing");
		
		/*
		 * Only add the stats that are interesting for the Imperial Agent.
		 */
		list.add("Primary Weapon Damage: " 
				+ this.format(this.getRangedDamagePrimaryMin()) + "-" 
				+ this.format(this.getRangedDamagePrimaryMax()));
		
		/* Accumulated boni for damage/healing: */
		list.add("Total ranged bonus damage: " 
				+ this.format(this.getRangedBonusDamage()));
		list.add("Total Tech bonus damage: " 
				+ this.format(this.getTotalBonusDamageTechForce()));
		list.add("Total Tech bonus healing: " 
				+ this.format(this.getTotalBonusHealingTechForce()));
		
		/* Partial bonus damage/healing: */
		list.add("Cunning damage bonus: " 
				+ this.format(this.getDamageBonusFromPrimaryStat()));
		list.add("Aim damage bonus: " 
				+ this.format(this.getDamageBonusFromSecondaryStat()));
		list.add("Tech damage bonus: " 
				+ this.format(this.getDamageBonusFromForceOrTechPower()));
		list.add("Power damage bonus: " 
				+ this.format(this.getDamageBonusFromPower()));
		list.add("Cunning healing bonus: " 
				+ this.format(this.getHealingBonusFromPrimaryStat()));
		list.add("Tech healing bonus: " 
				+ this.format(this.getHealingBonusFromForceOrTechPower()));
		list.add("Power healing bonus: " 
				+ this.format(this.getHealingBonusFromPower()));
		
		return list;
	}
	
	/**
	 * Format the double value.
	 * @param d Double value.
	 * @return Formatted double.
	 */
	private String format(final double d) {
		return this.df.format(d);
	}

	@Override
	public double getDamageBonusFromPrimaryStat() {
		return this.getTotalStats(PRIMARY_STAT) * 0.2;
	}
	
	@Override
	public double getDamageBonusFromSecondaryStat() {
		return this.getTotalStats(EItemStat.AIM) * 0.2;
	}

	@Override
	public double getDamageBonusFromForceOrTechPower() {
		return this.getTotalStats(EItemStat.TECHPOWER) * 0.23;
	}

	@Override
	public double getDamageBonusFromPower() {
		return this.getTotalStats(EItemStat.POWER) * 0.23;
	}

	@Override
	public double getHealingBonusFromPrimaryStat() {
		return this.getTotalStats(PRIMARY_STAT) * 0.14;
	}

	@Override
	public double getHealingBonusFromForceOrTechPower() {
		return this.getTotalStats(EItemStat.TECHPOWER) * 0.17;
	}

	@Override
	public double getHealingBonusFromPower() {
		return this.getTotalStats(EItemStat.POWER) * 0.17;
	}
	
	/* ******************** CRITICAL STRIKE CHANCE ************************** */
	
	/**
	 * Get the critical strike chance in percent from the talent "Lethality".
	 * @return Critical strike chance.
	 */
	private double getLethalityCritPercent() {
		return Model.getProfile().getSpecTemplate().getTree(
				ETalentTree.LETHALITY_S).get("Lethality").getPoints() * 2d;
	}
	
	/**
	 * Get the critical strike chance from the group buff.
	 * @return Critical strike chance.
	 */
	private double getGroupBuffCritPercent() {
		final BuffTemplate t = Model.getProfile().getBuffTemplate();
		double chance = 0d;
		
		for (int i = 0; i < t.size(); i++) {
			if (t.get(i).getName().equals("Lucky Shots / Coordination") 
					&& t.get(i).isActive()) {
				chance = CRITICAL_STRIKE_CHANCE_GROUP_BUFF;
			}
		}
		return chance;
	}

	@Override
	public double getCritChancePercent() {		
		/* The general critical strike chance of special attacks.
		 * Cunning + CritRating + Talent Lethality + Group Buff
		 */
		return 5d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(PRIMARY_STAT) / LEVEL) / 2.5)))
			+ 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.CRITICAL_RATING) / LEVEL) / 0.45)))
			+ this.getLethalityCritPercent() + this.getGroupBuffCritPercent();
	}
	
	@Override
	public double getMeleeCritChancePercent() {
		/* The critical strike chance of melee weapon attacks.
		 * Strength + CritRating + Talent Lethality + Group Buff
		 */
		return 5d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.STRENGTH) / LEVEL) / 2.5)))
			+ 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.CRITICAL_RATING) / LEVEL) / 0.45)))
			+ this.getLethalityCritPercent() + this.getGroupBuffCritPercent();
	}

	@Override
	public double getForceCritChancePercent() {
		/* This is not used by the Sniper. 
		 * Willpower + CritRating + Talent Lethality + Group Buff
		 */
		return 5d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.WILLPOWER) / LEVEL) / 2.5)))
			+ 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.CRITICAL_RATING) / LEVEL) / 0.45)))
			+ this.getLethalityCritPercent() + this.getGroupBuffCritPercent();
	}
	
	@Override
	public double getRangedCritChancePercent() {
		/* The critical strike chance for ranged weapon attacks.
		 * BaseCrit + Cunning + CritRating + Aim + 
		 * Talent Lethality + Group Buff */
		return 5d + 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(PRIMARY_STAT) / LEVEL) / 2.5)))
			+ 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.CRITICAL_RATING) / LEVEL) / 0.45)))
			+ 30d * (1d - Math.pow(1d - (0.01 / 0.3),
			((this.getTotalStats(EItemStat.AIM) / LEVEL) / 2.5)))
			+ this.getLethalityCritPercent() + this.getGroupBuffCritPercent();
	}

	@Override
	public double getShieldGlanceChancePercent() {
		return 0d;
	}

	@Override
	public double getShieldGlanceAbsorbtionPercent() {
		return 0d;
	}

	@Override
	public double getRangedDamagePrimaryMin() {
		/* Min. weapon damage + total ranged bonus damage */
		return this.getTotalStats(EItemStat.MIN_DAMAGE) 
				+ this.getRangedBonusDamage();
	}
	
	@Override
	public double getRangedDamagePrimaryMax() {
		/* Max. weapon damage + total ranged bonus damage */
		return this.getTotalStats(EItemStat.MAX_DAMAGE) 
		+ this.getRangedBonusDamage();
	}

	@Override
	public double getRangedBonusDamage() {
		/* Power + Aim + Cunning */
		return this.getDamageBonusFromPower() 
				+ this.getDamageBonusFromPrimaryStat() 
				+ this.getDamageBonusFromSecondaryStat();
	}

	@Override
	public double getTotalBonusDamageTechForce() {
		/* Cunning + Power + Techpower */
		return this.getDamageBonusFromPrimaryStat() 
			+ this.getDamageBonusFromForceOrTechPower() 
			+ this.getDamageBonusFromPower();
	}

	@Override
	public double getTotalBonusHealingTechForce() {
		/* Power + Techpower */
		return this.getHealingBonusFromPower() 
			+ this.getHealingBonusFromForceOrTechPower();
	}
}
